test(vi-mock): inherit the real @object-ui/plugin-list surface in 9 vi.mock factories (objectui#6892 slice 12) - #8250
Conversation
…i.mock factories
Slice 12 of objectui#6892. Nine `vi.mock('@object-ui/plugin-list', ...)`
factories under `packages/app-shell/src/views` hand-listed `ListView` and
froze the barrel's other eleven exports at whatever was typed that day. Each
now spreads the real module first and keeps its own `ListView` double as the
override after it, so only the surface the double sits on becomes real.
No assertion, no test behaviour and no product source is touched: the diff is
nine factory headers. The tenth site on this specifier
(`ObjectView.chartRelay-7823.test.tsx`) already inherited on main and is the
slice's free confirmation.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
The sweep in the previous commit takes this specifier to zero frozen call sites, which is the documented precondition for widening `COVERED_SPECIFIERS`. Sixteen members become seventeen; the header gains slice 12's per-specifier record (graph walk, import-cost reading on the aggregate, neighbour reading) and the remaining-population paragraph is refreshed 40 -> 31 frozen over 660 judged, with `@object-ui/app-shell` still PARKED under objectui#8173. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Empty frontmatter: nine `packages/app-shell` spec files and one CI script changed, no product source and no manifest. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
…ead) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
… the merged head) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Armed —
Landing stroke follows the merge: content probe on re-fetched Generated by Claude Code |
|
LANDED — Merged at 2026-09-07T05:11:20Z as Slice 13 ( Generated by Claude Code |
Part of #6892 (slice 12:
@object-ui/plugin-list)Nine
vi.mock('@object-ui/plugin-list', ...)factories underpackages/app-shell/src/viewshand-listedListViewand froze the barrel's othereleven exports at whatever was typed that day. Each now spreads the real module
first and keeps its own
ListViewdouble as the override after it, so only theSURFACE the double sits on becomes real. The specifier joins
COVERED_SPECIFIERSin the same PR, per the gate's documented widening precondition.
No assertion, no test behaviour, no product source and no
package.jsonistouched. The diff is nine factory headers, the gate's covered set and header
record, and one empty-frontmatter changeset.
Bases. Branch cut from
190cd07b3(the slice-11 merge).origin/mainmovedtwice mid-slice, so it was merged in twice with merge commits (never rebased) and
every reading below was re-taken on the merged head:
c85116068, then31a0d055b. Final headf4c9a8768; every exit code below is pinned to it.because GitHub's body sanitizer silently deletes tag-shaped fragments (AGENTS.md,
"GitHub 会改写你写进 issue/PR 正文的字节"). Where the repair's type argument is
described, the generic brackets around
typeof import('@object-ui/plugin-list')are omitted from the prose and live only in the diff.
STEP 0 — PROCEED
Method. The static import graph was walked from
packages/plugin-list/src/index.tsxwith the TypeScript parser, every module-scope statement of every reached module
classified, and import-time effects re-classified by a second AST pass that STOPS at
every function-like boundary (a statement-level regex over the same graph reports 18
"effects", and all 18 are matches inside arrow-function bodies that do not run at
import — prefer the parser). Aliases were read out of
vitest.config.mtsitself;TS ESM
'./x.js'specifiers resolved to./x.ts/./x.tsx. Corroboratedempirically by importing the real barrel under the light
domproject, which isthe single project all ten sites run in.
Graph. 539 modules, 5,777 module-scope statements (2,376 import/export, 1,226
fn/class, 967 bindings, 803 type declarations, 405 other) over twelve workspace
packages: components 206, core 95, fields 77, react 64, i18n 27, types 17, mobile
16, permissions 10, plugin-list 8, sdui-parser 8, data-objectstack 6, providers 5.
98 bare side-effect imports (the
@object-ui/componentsrenderer cascade) and oneCSS import, inert because the root Vitest config declares no
cssoption.Registrations — NOT inert, and every one namespaced. 115 module-scope
ComponentRegistry.register(...)calls, ZERO of them bare:ui85,element10,page7,action5,plugin-list2,view1. The five rows inpackages/components/src/renderers/layout/page.tsxthat READ as bare carrynamespace: 'ui'through the spreadpageMetaconstant declared atpage.tsx:662— the namespace lives in
register()'s THIRD argument, never in the key — so thedeprecation
console.warninregister()cannot fire. Beyond registration thegraph is allocation only: 173
React.forwardRef, 84new Set, 21createContext,16
Object.freeze, 13new Map, 13cva, 13createSafeTranslation, 10createDiscardProofCache, plus the benignpackages/fieldsclass slice 7 alreadymeasured (five
registerFieldRenderermap writes, onesetCellRendererResolverassignment,
registerAllFields()). ZERO timers, globals, storage,fetchandconnections at import time.
Empirically, importing the real barrel under the light
domproject exports12 names, moves
ComponentRegistry.getAllTypes()from 0 to 375 keys, and emitsZERO
console.warnand ZEROconsole.error(three runs, identical).Import cost — decided on the AGGREGATE
importof the barrel, lightdomprojectInterfaceListPage.mapConfig(24 modules added)ObjectView.createPredicates(8 modules added)ObjectDataPage.createAffordances(24 added)Each per-file figure is the median of three runs. The aggregate was taken as an
INTERLEAVED A/B against the committed tree under a
trap, because this shared boxdrifted by ten seconds across the hour (the same ten files read 37.25 s median
early and 26.6 s median later, in the same frozen state). One pair alone would have
"decided" the slice in either direction; the two together read as noise. Nowhere
near objectui#6580's STOP band.
Mechanism, measured rather than argued. Every one of the ten files already
holds 515 to 539 of the barrel's 539 modules at module scope — the three view
components under test (
ObjectView.tsx,InterfaceListPage.tsx,ObjectDataPage.tsx) each importListViewfrom this specifier and their owngraphs pull the rest. Inheriting adds 8 modules in seven files and 24 in the other
two. That is why the marginal is uniform and small, and not slice 10's bimodal
shape.
Slice 11's deferred-cost class does not fire. No
React.lazyand no dynamicimport()of@object-ui/plugin-listexists anywhere inpackages/orapps/(two greps, both empty). All three consuming views import it at MODULE SCOPE, so
the inheritance cost lands in the import phase where no timeout applies. No file
needed a module-scope import, and no timeout was touched.
STEP 1 — the census, through the gate's own
scan()Re-derived on
190cd07b3withscan()imported andcoveredwidened IN MEMORY tothe 22 workspace specifiers any
vi.mockcall site names.COVERED_SPECIFIERSwasnever widened-and-reverted on disk.
@object-ui/plugin-listbefore@object-ui/plugin-listafterAll-specifier population: 40 to 31 frozen over 660 judged, and a diff of the two
per-specifier tables with the
@object-ui/plugin-listrow removed is EMPTY — nosite moved the other way. The PM's 10 / 1 / 9 forecast is EXACT, as is the owning
location: all ten sit in one directory,
packages/app-shell/src/views.Three syntactic shapes, not one — a refinement on the dispatch, which described
the double as the schema-columns renderer. Only one of the nine is that:
ListViewdouble returns null;props.schemainto a module-levelbinding and returns null;
data-testidand aJSON-serialised slice of
props.schema.Each hand-listed exactly ONE of the barrel's twelve exports. Every double is
unchanged by this PR; only the surface it sits on becomes real. None of the nine
reads a prop the real
ListViewwould not — all three shapes readprops.schemaor a path under it, which
ListViewPropsdeclares (A5: no phantom prop taught).The free confirmation is present, and it is the strongest kind for this slice.
ObjectView.chartRelay-7823.test.tsxalready inherited the real barrel onmain,passes, and carries a frozen
sonnerneighbour of its own — so the real barrel wasknown to load in the light
domproject ALONGSIDE that neighbour before anythingwas converted. None of the ten is in
heavyDomTestsordomTsTests; all ten are.test.tsxunder the lightdomproject, so one environment is the whole answer.Neighbour reading — ZERO repairs, predicted in advance
Every OTHER factory in all ten files was classified through the gate's own
findCallSitesBEFORE any edit, then judged against the walked graph.@object-ui/auth@object-ui/permissions@object-ui/collaboration@object-ui/plugin-view@object-ui/i18n@object-ui/reactsonnerreact-router-domsonnerpairing is the interesting one, and the neighbour rule aspreviously stated would have predicted a repair. This barrel's graph DOES reach
sonner— three imports — and seven of the ten files carry a frozensonnerfactory, which is exactly the pairing that killed 15 files in slice 6. It is benign
here for a reason worth recording: the two renderer modules that import it read
toast.successand friends only INSIDE their click handlers, andpackages/components/src/ui/sonner.tsxre-exportstoastat module scope butdereferences the
Toasterbinding only inside a component body. The frozenfactories all provide
toast. So the sharper rule is: a frozen neighbour isdangerous when the newly-real graph reads a MISSING BINDING from it AT MODULE
SCOPE — reach alone is necessary, not sufficient. The two
react-router-domfactories are safe by reach: no module in the graph imports it at all (the two
matches are prose in comments).
No file among the ten mocks
@object-ui/app-shell— re-derived on this base,confirming the PM's
commreading of 0. The parked specifier (objectui#8173) staysparked and untouched.
Dependency-edge reading
packages/app-shell/package.jsondeclares@object-ui/plugin-listas aworkspace:^PEER dependency at line 104 and aworkspace:*DEV dependency atline 121;
apps/console/package.jsondeclares it as aworkspace:*DEV dependencyat line 82. All ten converted files live in
packages/app-shell, so app-shell'spair is the one that licenses the repair's type argument; the console edge is
recorded but unused by this slice.
pnpm check:phantom-depsexits 0 on the finalhead (40 released packages, 3,988 source files, 20,215 specifiers).
Covered set: sixteen to seventeen
Exactly one line added to
COVERED_SPECIFIERS('@object-ui/plugin-list', exactstring match), the header count word sixteen to seventeen, the slice-12
per-specifier record added in the shape slices 5 to 11 used, and the
remaining-population paragraph refreshed 40 to 31 with
@object-ui/app-shellre-annotated as PARKED under objectui#8173. The remaining rows are
@object-ui/app-shell(23, all frozen, parked) and@object-ui/fields(8 of 10).The record was written WITHOUT the shape objectui#8117's pin rejects; a mechanical
grep -nPover the whole gate source for that pattern returns 0 matches. The pinneeded no edit — confirmed by RUNNING it, exit 0, 67/67, file unedited: it still
derives its names from
COVERED_SPECIFIERSrather than copying them.Positive control — two legs, from the committed tree
Both legs ran under a
trapwith ABSOLUTE paths againstpackages/app-shell/src/views/ObjectView.createPredicates.test.tsx, HEAD blob8aa55c0ab80622062fbb4e7740ce0ec45a2cfc7d. Each mutation is proven ON DISK by acount anchored on the plugin-list spread line SPECIFICALLY (this file carries three
other spreads, so a file-wide count would never fall to zero) AND by a changed blob,
before the gate result is read. Each restore is proven by STATE, never by a checkout
exit code.
8aa55c0ab…(equals HEAD)25b5bca0f39b82b395ad3a6b7c11a16d3de02fbb1a708802033a95f473b535544c1254cdd530109eBoth legs name
ObjectView.createPredicates.test.tsx:103onvi.mock("@object-ui/plugin-list"). Leg 1 is objectui#8183's canonical controlshape, not the mention-only one. Leg 2's blob is BYTE-IDENTICAL to this file at the
base
190cd07b3, so the leg provably reconstructed the exact frozen form the gatemust reject. The anchored count read 1 to 0 on both legs; after each restore
git hash-objectequalled the HEAD blob andgit diff HEADnamed zero paths, andgit statuswas empty at the end. No dist preflight is owed: the gate is asource-text scanner that reads tracked paths from disk, and both configs alias the
specifier to source.
Gates — every exit code pinned to
f4c9a8768Each captured by redirect-then-capture, never through a pipe.
node scripts/check-vi-mock-inherit.mjs@object-ui/plugin-list; 4414 tracked source files, 2686 test-named, 609 carry a mockscripts/__tests__/check-vi-mock-inherit.test.tsscripts/__tests__/directoryvitest packages/app-shell/, four shardstype-checktsconfig.test.json --listFilesnaming all nine edited files in a 4,448-file programpnpm type-check:scriptspnpm lint:rootlinteslint --no-inline-config --format jsonover the 10 non-changeset pathspnpm check:phantom-depspnpm check:control-bytesgrep -naPcontrol-byte self-scan of every changed path (grep exit 1, no match)node scripts/check-changeset-presence.mjspackages/app-shellsource files of one released package)node scripts/check-changeset-no-major.mjsnode scripts/check-governed-queue-guard.mjs --testpnpm check:entry-guardpnpm check:vi-mock-specifiersThe whole-package lints cover every changed path, so the targeted
eslintrunabove is corroboration rather than a narrowing. Type-aware linting is NOT enabled
in
eslint.config.js(noproject/projectService), so this diff cannot movethe verdict on any file it does not touch.
Live E2E (informational)is base-red on every branch today for an upstream reason(#7990 / objectstack#16186) and is not this PR's.
Slice 13 forecast
@object-ui/fields— 10 judged / 2 inheriting / 8 frozen onf4c9a8768, the onlyremaining row once
@object-ui/app-shell(23 of 23) is skipped as PARKED underobjectui#8173. It has a free confirmation, as this slice did.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
Generated by Claude Code